#include <asm.h>
#include <csr.h>

.macro SAVE_CONTEXT
  .local _restore_kernel_tpsp
  .local _save_context
  /*
   * If coming from userspace, preserve the user thread pointer and load
   * the kernel thread pointer.  If we came from the kernel, sscratch
   * will contain 0, and we should continue on the current TP.
   */
  csrrw tp, CSR_SSCRATCH, tp
  bnez tp, _save_context

_restore_kernel_tpsp:
  csrr tp, CSR_SSCRATCH
  sd sp, PCB_KERNEL_SP(tp)
_save_context:
  sd sp, PCB_USER_SP(tp)
  ld sp, PCB_KERNEL_SP(tp)
  addi sp, sp, -(OFFSET_SIZE)

  /* TODO: save all general purpose registers here! */

  /*
   * Disable user-mode memory access as it should only be set in the
   * actual user copy routines.
   *
   * Disable the FPU to detect illegal usage of floating point in kernel
   * space.
   */
  li t0, SR_SUM | SR_FS

  /* TODO: save sstatus, sepc, stval, scause and sscratch on user stack */
.endm

.macro RESTORE_CONTEXT
  /* TODO: Restore all registers and sepc,sstatus */
.endm

ENTRY(enable_preempt)
  ld t1, current_running
  ld t0, PCB_PREEMPT_COUNT(t1)
  beq t0, zero, do_enable
  addi t0, t0, -1
  sd t0, PCB_PREEMPT_COUNT(t1)
  beq t0, zero, do_enable
  jr ra
do_enable:
  not t0, x0
  csrs CSR_SIE, t0
  jr ra
ENDPROC(enable_preempt)

ENTRY(disable_preempt)
  csrw CSR_SIE, zero
  ld t1, current_running
  ld t0, PCB_PREEMPT_COUNT(t1)
  addi t0, t0, 1
  sd t0, PCB_PREEMPT_COUNT(t1)
  jr ra
ENDPROC(disable_preempt)

ENTRY(enable_interrupt)
  li t0, SR_SIE
  csrs CSR_SSTATUS, t0
  jr ra
ENDPROC(enable_interrupt)

ENTRY(disable_interrupt)
  li t0, SR_SIE
  csrs CSR_SSTATUS, t0
  jr ra
ENDPROC(disable_interrupt)

// the address of previous pcb in a0
// the address of next pcb in a1  ！！！！！！！！这里非常重要，不能忽略。！！！！！！！！！！！！！！符合汇编函数的约定习惯
ENTRY(switch_to)
  // save all callee save registers on kernel stack
  //addi sp, sp, -(SWITCH_TO_SIZE)
  /* TODO: store all callee save registers,
   * see the definition of `struct switchto_context` in sched.h
   这里我们不再使用sp指针来存储地址，因为保存现场要保存sp的值到kerne栈，这里把值改掉还保存啥啊。干脆使用t1得了*/
  
  

   ld t1,0(a0)
   addi t1,t1,-(OFFSET_SIZE)
   addi t1,t1,-(SWITCH_TO_SIZE)
   sd ra,0(t1)
   sd sp,8(t1)
   sd s0,16(t1)
   sd s1,24(t1)
   sd s2,32(t1)
   sd s3,40(t1)
   sd s4,48(t1)
   sd s5,56(t1)
   sd s6,64(t1)
   sd s7,72(t1)
   sd s8,80(t1)
   sd s9,88(t1)
   sd s10,96(t1)
   sd s11,104(t1)


   ld t1,0(a1)
   addi t1,t1,-(OFFSET_SIZE)
   addi t1,t1,-(SWITCH_TO_SIZE)
   ld ra,0(t1)
   ld sp,8(t1)
   ld s0,16(t1)
   ld s1,24(t1)
   ld s2,32(t1)
   ld s3,40(t1)
   ld s4,48(t1)
   ld s5,56(t1)
   ld s6,64(t1)
   ld s7,72(t1)
   ld s8,80(t1)
   ld s9,88(t1)
   ld s10,96(t1)
   ld s11,104(t1)

   add tp,zero,a1

   //确保tp寄存器的值等于current_running即正在运行pcb的地址，这里就是a1的值，因为a1是switch_to的下一个进程，也就是切换结束的curren

  // restore next
  /* TODO: restore all callee save registers,
   * see the definition of `struct switchto_context` in sched.h*/

 // addi sp, sp, SWITCH_TO_SIZE

  //add a0, a1, zero
  //la ra, ret_from_exception
  jr ra //这里移交程序的控制权到下一个进程。这里是do_scheduler函数的最重要功能。
  //因为调用的ret_from_exception是利用sret跳转，那么sepc的值就是跳转地址。
ENDPROC(switch_to)

ENTRY(ret_from_exception)//约定好a0传进程地址也就是kernel栈的地址
  /* TODO: */
    ld t6, 0(tp)
    addi t6,t6,-(OFFSET_SIZE)
    ld ra, OFFSET_REG_RA(t6)
    ld sp, OFFSET_REG_SP(t6)
    //ld gp, OFFSET_REG_GP(t6)
    //ld tp, OFFSET_REG_TP(t1)此处有一个小问题，那就是进行进程调度，tp的值会改变。所以不应该再从内存恢复其值。
    //s类寄存器是汇编语言使用的变量，正在执行的进程可能要用这些量一定要保存，其实与switch_to逻辑一样。
    ld s0, OFFSET_REG_S0(t6)
    ld s1, OFFSET_REG_S1(t6)
    ld s2, OFFSET_REG_S2(t6)
    ld s3, OFFSET_REG_S3(t6)
    ld s4, OFFSET_REG_S4(t6)
    ld s5, OFFSET_REG_S5(t6)
    ld s6, OFFSET_REG_S6(t6)
    ld s7, OFFSET_REG_S7(t6)
    ld s8, OFFSET_REG_S8(t6)
    ld s9, OFFSET_REG_S9(t6)
    ld s10, OFFSET_REG_S10(t6)
    ld s11, OFFSET_REG_S11(t6)
    //a类寄存器一定要记得保存。a7是调用号，a0,a1,a2是参数
    ld a0, OFFSET_REG_A0(t6)
    ld a1, OFFSET_REG_A1(t6)
    ld a2, OFFSET_REG_A2(t6)
    ld a3, OFFSET_REG_A3(t6)
    ld a4, OFFSET_REG_A4(t6)
    ld a5, OFFSET_REG_A5(t6)
    ld a6, OFFSET_REG_A6(t6)
    ld a7, OFFSET_REG_A7(t6)
    //特权寄存器保存
    ld t0, OFFSET_REG_SCAUSE(t6)
    csrw CSR_SCAUSE, t0
    ld t0, OFFSET_REG_SSTATUS(t6)
    csrw CSR_SSTATUS, t0
    ld t0, OFFSET_REG_SEPC(t6)
    csrw CSR_SEPC, t0
    ld t0, OFFSET_REG_STVAL(t6)
    csrw CSR_STVAL, t0
    //t类寄存器t1就不存了
    ld t0, OFFSET_REG_T0(t6)
    ld t1, OFFSET_REG_T1(t6)
    ld t2, OFFSET_REG_T2(t6)
    ld t3, OFFSET_REG_T3(t6)
    ld t4, OFFSET_REG_T4(t6)
    ld t5, OFFSET_REG_T5(t6)
    ld t6, OFFSET_REG_T6(t6)
    sret
ENDPROC(ret_from_exception)

ENTRY(exception_handler_entry)
  //屏蔽中断。尚未完成。
    csrw CSR_SSCRATCH, t6
    ld t6,0(tp)//此处内核栈的地址还需要商榷。
    addi t6,t6,-(OFFSET_SIZE)//指针移动到regs_context段的起点
    sd zero, OFFSET_REG_ZERO(t6)
    sd ra, OFFSET_REG_RA(t6)
    sd sp, OFFSET_REG_SP(t6)
    //sd gp, OFFSET_REG_GP(t6)
    sd tp, OFFSET_REG_TP(t6)
    //s类寄存器是汇编语言使用的变量，正在执行的进程可能要用这些量一定要保存，其实与switch_to逻辑一样。
    sd s0, OFFSET_REG_S0(t6)
    sd s1, OFFSET_REG_S1(t6)
    sd s2, OFFSET_REG_S2(t6)
    sd s3, OFFSET_REG_S3(t6)
    sd s4, OFFSET_REG_S4(t6)
    sd s5, OFFSET_REG_S5(t6)
    sd s6, OFFSET_REG_S6(t6)
    sd s7, OFFSET_REG_S7(t6)
    sd s8, OFFSET_REG_S8(t6)
    sd s9, OFFSET_REG_S9(t6)
    sd s10, OFFSET_REG_S10(t6)
    sd s11, OFFSET_REG_S11(t6)
    //a类寄存器一定要记得保存。a7是调用号，a0,a1,a2是参数
    sd a0, OFFSET_REG_A0(t6)
    sd a1, OFFSET_REG_A1(t6)
    sd a2, OFFSET_REG_A2(t6)
    sd a3, OFFSET_REG_A3(t6)
    sd a4, OFFSET_REG_A4(t6)
    sd a5, OFFSET_REG_A5(t6)
    sd a6, OFFSET_REG_A6(t6)
    sd a7, OFFSET_REG_A7(t6)
    //t类寄存器t6就不存了
    sd t0, OFFSET_REG_T0(t6)
    sd t2, OFFSET_REG_T2(t6)
    sd t3, OFFSET_REG_T3(t6)
    sd t4, OFFSET_REG_T4(t6)
    sd t5, OFFSET_REG_T5(t6)
    sd t1, OFFSET_REG_T1(t6)
    csrr t0, CSR_SSCRATCH
    sd t0, OFFSET_REG_T6(t6)
    //特权寄存器保存
    csrr t0,CSR_SCAUSE
    sd t0, OFFSET_REG_SCAUSE(t6)
    csrr t0,CSR_SSTATUS
    sd t0, OFFSET_REG_SSTATUS(t6)
    csrr t0,CSR_SEPC
    sd t0, OFFSET_REG_SEPC(t6)
    csrr t0,CSR_STVAL     //找不到这个寄存器。。。。csr.h里没有这个宏定义
    sd t0, OFFSET_REG_STVAL(t6)
  /* Load the global pointer */
  .option push
  .option norelax
  la gp, __global_pointer$
  .option pop

  /* TODO: load ret_from_exception into $ra
   * so that we can return to ret_from_exception
   * when interrupt_help complete.
   */

  /* TODO: call interrupt_helper
   * note: don forget to pass parameter*/
   //为跳转传参数regs_context_t *regs, uint64_t stval, uint64_t cause
   //与系统调用直接相关的调用号等参数已经存在了内存里，现在进行函数调用传参也无妨。
    add a0, t6, zero
    csrr a1,CSR_STVAL
    csrr a2,CSR_SCAUSE
    add a3, a7, zero
    call interrupt_helper  
    //中断返回。
    add a0, tp, zero
    la ra,ret_from_exception
    jr ra
  csrw CSR_SSCRATCH, x0

ENDPROC(exception_handler_entry)
ENTRY(switch_to_non_ecall)
  // save all callee save registers on kernel stack
  //addi sp, sp, -(SWITCH_TO_SIZE)
  /* TODO: store all callee save registers,
   * see the definition of `struct switchto_context` in sched.h
   这里我们不再使用sp指针来存储地址，因为保存现场要保存sp的值到kerne栈，这里把值改掉还保存啥啊。干脆使用t1得了*/
  
  

   ld t1,0(a0)
   addi t1,t1,-(OFFSET_SIZE)
   addi t1,t1,-(SWITCH_TO_SIZE)
   sd ra,0(t1)
   sd sp,8(t1)
   sd s0,16(t1)
   sd s1,24(t1)
   sd s2,32(t1)
   sd s3,40(t1)
   sd s4,48(t1)
   sd s5,56(t1)
   sd s6,64(t1)
   sd s7,72(t1)
   sd s8,80(t1)
   sd s9,88(t1)
   sd s10,96(t1)
   sd s11,104(t1)


   ld t1,0(a1)
   addi t1,t1,-(OFFSET_SIZE)
   addi t1,t1,-(SWITCH_TO_SIZE)
   ld ra,0(t1)
   ld sp,8(t1)
   ld s0,16(t1)
   ld s1,24(t1)
   ld s2,32(t1)
   ld s3,40(t1)
   ld s4,48(t1)
   ld s5,56(t1)
   ld s6,64(t1)
   ld s7,72(t1)
   ld s8,80(t1)
   ld s9,88(t1)
   ld s10,96(t1)
   ld s11,104(t1)

   add tp,zero,a1

   //确保tp寄存器的值等于current_running即正在运行pcb的地址，这里就是a1的值，因为a1是switch_to的下一个进程，也就是切换结束的curren

  // restore next
  /* TODO: restore all callee save registers,
   * see the definition of `struct switchto_context` in sched.h*/

 // addi sp, sp, SWITCH_TO_SIZE

  
  jr ra //这里移交程序的控制权到下一个进程。这里是do_scheduler函数的最重要功能。
  //因为调用的ret_from_exception是利用sret跳转，那么sepc的值就是跳转地址。
ENDPROC(switch_to)